package org.cocos2dx.javascript;

import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.Toast;

import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.Result;
import com.google.android.gms.games.Games;
import com.google.android.gms.games.GamesClientStatusCodes;
import com.google.android.gms.games.SnapshotsClient;
import com.google.android.gms.games.snapshot.Snapshot;
import com.google.android.gms.games.snapshot.SnapshotMetadata;
import com.google.android.gms.games.snapshot.SnapshotMetadataChange;
import com.google.android.gms.tasks.Continuation;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;

import org.cocos2d.ShunLiu.R;

import java.io.IOException;
import java.util.Calendar;

import static android.app.Activity.RESULT_OK;

public class SavedGamesManager {

    private static SavedGamesManager mInstance = null;

    public static SavedGamesManager getInstance() {
        if (null == mInstance) {
            mInstance = new SavedGamesManager();
        }
        return mInstance;
    }

    private static InstantAppActivity this_tmp;
    // Client used to interact with Google Snapshots.
    private SnapshotsClient mSnapshotsClient = null;
    private static final String TAG = "SavedGamesManager";
    // progress dialog we display while we're loading state from the cloud
    ProgressDialog mLoadingDialog = null;
    // Request code for saving the game to a snapshot.
    private static final int RC_SAVE_SNAPSHOT = 9004;
    private static final int RC_LOAD_SNAPSHOT = 9005;
    private String currentSaveName = "snapshot";
    private static String loadedData;

    // intent data which is a snapshot metadata
    public static final String SNAPSHOT_METADATA = "snapshotmeta";
    // intent data that is a list of snapshot metadatas.
    public static final String SNAPSHOT_METADATA_LIST = "snapshotmetaList";
    // intent data that is the conflict id.  used when resolving a conflict.
    public static final String CONFLICT_ID = "conflictId";
    // intent data that is the retry count for retrying the conflict resolution.
    public static final String RETRY_COUNT = "retrycount";
    // Members related to the conflict resolution chooser of Snapshots.
    final static int MAX_SNAPSHOT_RESOLVE_RETRIES = 50;

    public void init(SnapshotsClient snapshotsClient) {
        mSnapshotsClient = snapshotsClient;
    }

    public void init(InstantAppActivity context, GoogleSignInAccount googleSignInAccount) {
        Log.d(TAG, "init");
        this_tmp = context;
        mSnapshotsClient = Games.getSnapshotsClient(context, googleSignInAccount);
    }

    public void onStop() {
        if (mLoadingDialog != null) {
            mLoadingDialog.dismiss();
            mLoadingDialog = null;
        }
    }

    /**
     * Loads a Snapshot from the user's synchronized storage.
     */
    public void loadFromSnapshot(final SnapshotMetadata snapshotMetadata) {
        if (mLoadingDialog == null) {
            mLoadingDialog = new ProgressDialog(this_tmp);
            mLoadingDialog.setMessage(this_tmp.getString(R.string.loading_from_cloud));
        }

        mLoadingDialog.show();

        waitForClosedAndOpen(snapshotMetadata)
                .addOnSuccessListener(new OnSuccessListener<SnapshotsClient.DataOrConflict<Snapshot>>() {
                    @Override
                    public void onSuccess(SnapshotsClient.DataOrConflict<Snapshot> result) {

                        // if there is a conflict  - then resolve it.
                        Snapshot snapshot = null;
                        try {
                            snapshot = processOpenDataOrConflict(RC_LOAD_SNAPSHOT, result, 0);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        if (snapshot == null) {
                            Log.w(TAG, "Conflict was not resolved automatically, waiting for user to resolve.");
                        } else {
                            try {
                                readSavedGame(snapshot);
                                Log.i(TAG, "Snapshot loaded.");
                            } catch (IOException e) {
                                Log.e(TAG, "Error while reading snapshot contents: " + e.getMessage());
                            }
                        }

                        SnapshotCoordinator.getInstance().discardAndClose(mSnapshotsClient, snapshot)
                                .addOnFailureListener(new OnFailureListener() {
                                    @Override
                                    public void onFailure(@NonNull Exception e) {
                                        handleException(e, "There was a problem discarding the snapshot!");
                                    }
                                });

                        if (mLoadingDialog != null && mLoadingDialog.isShowing()) {
                            mLoadingDialog.dismiss();
                            mLoadingDialog = null;
                        }
                    }
                });
    }

    private Task<SnapshotsClient.DataOrConflict<Snapshot>> waitForClosedAndOpen(final SnapshotMetadata snapshotMetadata) {

        final boolean useMetadata = snapshotMetadata != null && snapshotMetadata.getUniqueName() != null;
        if (useMetadata) {
            Log.i(TAG, "Opening snapshot using metadata: " + snapshotMetadata);
        } else {
            Log.i(TAG, "Opening snapshot using currentSaveName: " + currentSaveName);
        }

        final String filename = useMetadata ? snapshotMetadata.getUniqueName() : currentSaveName;

        return SnapshotCoordinator.getInstance()
                .waitForClosed(filename)
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        handleException(e, "There was a problem waiting for the file to close!");
                    }
                })
                .continueWithTask(new Continuation<Result, Task<SnapshotsClient.DataOrConflict<Snapshot>>>() {
                    @Override
                    public Task<SnapshotsClient.DataOrConflict<Snapshot>> then(@NonNull Task<Result> task) throws Exception {
                        Task<SnapshotsClient.DataOrConflict<Snapshot>> openTask = useMetadata
                                ? SnapshotCoordinator.getInstance().open(mSnapshotsClient, snapshotMetadata)
                                : SnapshotCoordinator.getInstance().open(mSnapshotsClient, filename, true);
                        return openTask.addOnFailureListener(new OnFailureListener() {
                            @Override
                            public void onFailure(@NonNull Exception e) {
                                handleException(e,
                                        useMetadata
                                                ? this_tmp.getString(R.string.error_opening_metadata)
                                                : this_tmp.getString(R.string.error_opening_filename)
                                );
                            }
                        });
                    }
                });
    }

    /**
     * Conflict resolution for when Snapshots are opened.
     *
     * @param requestCode - the request currently being processed.  This is used to forward on the
     *                    information to another activity, or to send the result intent.
     * @param result      The open snapshot result to resolve on open.
     * @param retryCount  - the current iteration of the retry.  The first retry should be 0.
     * @return The opened Snapshot on success; otherwise, returns null.
     */
    Snapshot processOpenDataOrConflict(int requestCode, SnapshotsClient.DataOrConflict<Snapshot> result, int retryCount) throws IOException {

        retryCount++;

        if (!result.isConflict()) {
            return result.getData();
        }

        Log.i(TAG, "Open resulted in a conflict!");

        SnapshotsClient.SnapshotConflict conflict = result.getConflict();
        final Snapshot snapshot = conflict.getSnapshot();
        final Snapshot conflictSnapshot = conflict.getConflictingSnapshot();

        Snapshot choseOne;
        if (snapshot.getMetadata().getLastModifiedTimestamp() >= conflictSnapshot.getMetadata().getLastModifiedTimestamp()) {
            choseOne = snapshot;
            Log.i(TAG, "自动选择了服务器数据！");
        } else {
            choseOne = conflictSnapshot;
            Log.i(TAG, "自动选择了冲突数据！");
        }
        /*
        Log.i(TAG, "服务器数据:" + snapshot.getMetadata().getPlayedTime());
        Log.i(TAG, "冲突数据:" + conflictSnapshot.getMetadata().getPlayedTime());

        Log.i(TAG, "服务器数据:" + new String(snapshot.getSnapshotContents().readFully()));
        Log.i(TAG, "冲突数据:" + new String(snapshot.getSnapshotContents().readFully()));

        if (snapshot.getMetadata().getPlayedTime() >= conflictSnapshot.getMetadata().getPlayedTime()) {
        } else {
        }
        */

        //return null;
        return choseOne;
    }

    private void readSavedGame(Snapshot snapshot) throws IOException {
        loadedData = new String(snapshot.getSnapshotContents().readFully());
        //Log.d(TAG, "打印读取的数据：" + loadedData);
        this_tmp.InfoToJs("Snapshot", loadedData);
    }

    /**
     * Since a lot of the operations use tasks, we can use a common handler for whenever one fails.
     *
     * @param exception The exception to evaluate.  Will try to display a more descriptive reason for the exception.
     * @param details   Will display alongside the exception if you wish to provide more details for why the exception
     *                  happened
     */
    public void handleException(Exception exception, String details) {
        int status = 0;

        if (exception instanceof ApiException) {
            ApiException apiException = (ApiException) exception;
            status = apiException.getStatusCode();
        }

        String message = this_tmp.getString(R.string.status_exception_error, details, status, exception);

        new AlertDialog.Builder(this_tmp)
                .setMessage(message)
                .setNeutralButton(android.R.string.ok, null)
                .show();

        // Note that showing a toast is done here for debugging. Your application should
        // resolve the error appropriately to your app.
        if (status == GamesClientStatusCodes.SNAPSHOT_NOT_FOUND) {
            Log.i(TAG, "Error: Snapshot not found");
            Toast.makeText(this_tmp.getBaseContext(), "Error: Snapshot not found",
                    Toast.LENGTH_SHORT).show();
        } else if (status == GamesClientStatusCodes.SNAPSHOT_CONTENTS_UNAVAILABLE) {
            Log.i(TAG, "Error: Snapshot contents unavailable");
            Toast.makeText(this_tmp.getBaseContext(), "Error: Snapshot contents unavailable",
                    Toast.LENGTH_SHORT).show();
        } else if (status == GamesClientStatusCodes.SNAPSHOT_FOLDER_UNAVAILABLE) {
            Log.i(TAG, "Error: Snapshot folder unavailable");
            Toast.makeText(this_tmp.getBaseContext(), "Error: Snapshot folder unavailable.",
                    Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Prepares saving Snapshot to the user's synchronized storage, conditionally resolves errors,
     * and stores the Snapshot.
     */
    void saveSnapshot(final SnapshotMetadata snapshotMetadata) {
        waitForClosedAndOpen(snapshotMetadata)
                .addOnCompleteListener(new OnCompleteListener<SnapshotsClient.DataOrConflict<Snapshot>>() {
                    @Override
                    public void onComplete(@NonNull Task<SnapshotsClient.DataOrConflict<Snapshot>> task) {
                        SnapshotsClient.DataOrConflict<Snapshot> result = task.getResult();
                        Snapshot snapshotToWrite = null;
                        try {
                            snapshotToWrite = processOpenDataOrConflict(RC_SAVE_SNAPSHOT, result, 0);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        if (snapshotToWrite == null) {
                            // No snapshot available yet; waiting on the user to choose one.
                            return;
                        }

                        Log.d(TAG, "Writing data to snapshot: " + snapshotToWrite.getMetadata().getUniqueName());
                        writeSnapshot(snapshotToWrite)
                                .addOnCompleteListener(new OnCompleteListener<SnapshotMetadata>() {
                                    @Override
                                    public void onComplete(@NonNull Task<SnapshotMetadata> task) {
                                        if (task.isSuccessful()) {
                                            Log.i(TAG, "Snapshot saved!");
                                        } else {
                                            handleException(task.getException(), this_tmp.getString(R.string.write_snapshot_error));
                                        }
                                    }
                                });
                    }
                });
    }

    /**
     * Generates metadata, takes a screenshot, and performs the write operation for saving a
     * snapshot.
     */
    private Task<SnapshotMetadata> writeSnapshot(Snapshot snapshot) {
        // Set the data payload for the snapshot.
        //Log.d(TAG, "cc数据保存前 " + SavedGamesManager.getInstance().loadedData);
        byte[] ccJson = SavedGamesManager.getInstance().loadedData.getBytes();
        //Log.d(TAG, "cc数据转化 " + ccJson);
        snapshot.getSnapshotContents().writeBytes(ccJson);

        // Save the snapshot.
        SnapshotMetadataChange metadataChange = new SnapshotMetadataChange.Builder()
                .setDescription("Modified data at: " + Calendar.getInstance().getTime())
                .build();
        return SnapshotCoordinator.getInstance().commitAndClose(mSnapshotsClient, snapshot, metadataChange);
    }

    /**
     * Handles resolving the snapshot conflict asynchronously.
     *
     * @param requestCode      - the request currently being processed.  This is used to forward on the
     *                         information to another activity, or to send the result intent.
     * @param conflictId       - the id of the conflict being resolved.
     * @param retryCount       - the current iteration of the retry.  The first retry should be 0.
     * @param snapshotMetadata - the metadata of the snapshot that is selected to resolve the conflict.
     */
    private Task<SnapshotsClient.DataOrConflict<Snapshot>> resolveSnapshotConflict(final int requestCode,
                                                                                   final String conflictId,
                                                                                   final int retryCount,
                                                                                   final SnapshotMetadata snapshotMetadata) {

        Log.i(TAG, "Resolving conflict retry count = " + retryCount + " conflictid = " + conflictId);
        return waitForClosedAndOpen(snapshotMetadata)
                .continueWithTask(new Continuation<SnapshotsClient.DataOrConflict<Snapshot>, Task<SnapshotsClient.DataOrConflict<Snapshot>>>() {
                    @Override
                    public Task<SnapshotsClient.DataOrConflict<Snapshot>> then(@NonNull Task<SnapshotsClient.DataOrConflict<Snapshot>> task) throws Exception {
                        return SnapshotCoordinator.getInstance().resolveConflict(
                                mSnapshotsClient,
                                conflictId,
                                task.getResult().getData())
                                .addOnCompleteListener(new OnCompleteListener<SnapshotsClient.DataOrConflict<Snapshot>>() {
                                    @Override
                                    public void onComplete(@NonNull Task<SnapshotsClient.DataOrConflict<Snapshot>> task) {
                                        if (!task.isSuccessful()) {
                                            handleException(
                                                    task.getException(),
                                                    "There was a problem opening a file for resolving the conflict!");
                                            return;
                                        }

                                        Snapshot snapshot = null;
                                        try {
                                            snapshot = processOpenDataOrConflict(requestCode,
                                                    task.getResult(),
                                                    retryCount);
                                        } catch (IOException e) {
                                            e.printStackTrace();
                                        }
                                        Log.d(TAG, "resolved snapshot conflict - snapshot is " + snapshot);
                                        // if there is a snapshot returned, then pass it along to onActivityResult.
                                        // otherwise, another activity will be used to resolve the conflict so we
                                        // don't need to do anything here.
                                        if (snapshot != null) {
                                            Intent intent = new Intent("");
                                            intent.putExtra(SNAPSHOT_METADATA, snapshot.getMetadata().freeze());
                                            onActivityResult(requestCode, RESULT_OK, intent);
                                        }
                                    }
                                });
                    }
                });
    }

    /**
     * You can capture the Snapshot selection intent in the onActivityResult method. The result
     * either indicates a new Snapshot was created (EXTRA_SNAPSHOT_NEW) or was selected.
     */
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        // loading a snapshot into the game.
        if (requestCode == RC_LOAD_SNAPSHOT) {
            Log.d(TAG, "Loading a snapshot resultCode = " + resultCode);
            if (resultCode == RESULT_OK) {
                if (intent != null && intent.hasExtra(SNAPSHOT_METADATA)) {
                    // Load a snapshot.
                    String conflictId = intent.getStringExtra(CONFLICT_ID);
                    int retryCount = intent.getIntExtra(RETRY_COUNT,
                            MAX_SNAPSHOT_RESOLVE_RETRIES);
                    SnapshotMetadata snapshotMetadata =
                            intent.getParcelableExtra(SNAPSHOT_METADATA);
                    if (conflictId == null) {
                        loadFromSnapshot(snapshotMetadata);
                    } else {
                        Log.d(TAG, "resolving " + snapshotMetadata);
                        resolveSnapshotConflict(requestCode, conflictId, retryCount, snapshotMetadata);
                    }
                }
            }
        }
        // saving the game into a snapshot.
        else if (requestCode == RC_SAVE_SNAPSHOT) {
            if (resultCode == RESULT_OK) {
                if (intent != null && intent.hasExtra(SNAPSHOT_METADATA)) {
                    // Load a snapshot.
                    String conflictId = intent.getStringExtra(CONFLICT_ID);
                    int retryCount = intent.getIntExtra(RETRY_COUNT,
                            MAX_SNAPSHOT_RESOLVE_RETRIES);
                    SnapshotMetadata snapshotMetadata =
                            intent.getParcelableExtra(SNAPSHOT_METADATA);
                    if (conflictId == null) {
                        saveSnapshot(snapshotMetadata);
                    } else {
                        Log.d(TAG, "resolving " + snapshotMetadata);
                        resolveSnapshotConflict(requestCode, conflictId, retryCount, snapshotMetadata);
                    }
                }
            }
        }
    }

    public static void saveSnapshotData(String jsonData) {
        Log.d(TAG, "保存玩家数据：" + jsonData);
        //存储的是这个信息
        SavedGamesManager.getInstance().loadedData = jsonData;
        SavedGamesManager.getInstance().saveSnapshot(null);
    }

    public void deleteSnapshotData(SnapshotMetadata snapshotMetadata) {
        SnapshotCoordinator.getInstance().delete(mSnapshotsClient, snapshotMetadata);
    }
}